
using System.Collections;
using System.ComponentModel;
using System.Reflection;
using LinqToDB.Extensions;
using LinqToDB.SqlQuery;

namespace LinqToDB.Data;

public static class GarDataConnectionExtensions
{
    private static readonly MethodInfo createTableBaseMethod;
    private static readonly MethodInfo dropTableBaseMethod;
    private static readonly MethodInfo insertOrReplaceBaseMethod;
    private static readonly MethodInfo bulkCopyBaseMethod;
    private static readonly MethodInfo getTableBaseMethod;

    static GarDataConnectionExtensions()
    {
        var dataExtensionsType = typeof(DataExtensions);
        var dataExtensionsMethods = dataExtensionsType.GetMethods(BindingFlags.Static | BindingFlags.Public);

        GarDataConnectionExtensions.createTableBaseMethod = dataExtensionsMethods.Where(m => m.Name == nameof(DataExtensions.CreateTable)).Single();

        GarDataConnectionExtensions.dropTableBaseMethod = dataExtensionsMethods.Where(m => 
        {
            if (m.Name != nameof(DataExtensions.DropTable))
                return false;

            var parameters = m.GetParameters();

            if (parameters.Length == 7 && parameters[0].Name == "dataContext")
                return true;

            return false;
        }).Single();

        GarDataConnectionExtensions.insertOrReplaceBaseMethod = dataExtensionsMethods.Where(m => 
        {
            if (m.Name != nameof(DataExtensions.InsertOrReplace))
                return false;

            var parameters = m.GetParameters();

            if (parameters.Length == 7 && parameters[0].Name == "dataContext")
                return true;

            return false;
        }).Single();

        var dataConnectionExtensions = typeof(DataConnectionExtensions);
        var dataConnectionExtensionsMethods = dataConnectionExtensions.GetMethods(BindingFlags.Static | BindingFlags.Public);

        GarDataConnectionExtensions.bulkCopyBaseMethod = dataConnectionExtensionsMethods.Where(m => 
        {
            if (m.Name != nameof(DataConnectionExtensions.BulkCopy))
                return false;

            var parameters = m.GetParameters();

            if (parameters.Length == 3 
                && parameters[0].Name == "dataConnection"
                && parameters[1].Name == "options"
            )
            {
                return true;
            }

            return false;
        }).Single();    

        GarDataConnectionExtensions.getTableBaseMethod = dataExtensionsMethods.Where(m => 
        {
            if (m.Name != nameof(DataExtensions.GetTable))
                return false;

            var parameters = m.GetParameters();

            if (parameters.Length == 1 && parameters[0].Name == "dataContext")
                return true;

            return false;                    
        }).Single();
    }

    public static object? CreateTable(
        this IDataContext dataContext
        , Type tableType
        , string? tableName = null
        , string? databaseName = null
        , string? schemaName = null
        , string? statementHeader = null
        , string? statementFooter = null
        , DefaultNullable defaultNullable = DefaultNullable.None
        , string? serverName = null
        , TableOptions tableOptions = TableOptions.NotSet
        )
    {
        ArgumentNullException.ThrowIfNull(dataContext, nameof(dataContext));
        ArgumentNullException.ThrowIfNull(tableType, nameof(tableType));

        var genericMethod = createTableBaseMethod.MakeGenericMethod(tableType)!;        

        return genericMethod.Invoke(null, [dataContext,  tableName, databaseName, schemaName, statementHeader, statementFooter, defaultNullable, serverName, tableOptions]);
    }

    public static void DropTable(
        this IDataContext dataContext
        , Type tableType
        , string? tableName = null
        , string? databaseName = null
        , string? schemaName = null
        , bool? throwExceptionIfNotExists = null
        , string? serverName = null
        , TableOptions tableOptions = TableOptions.NotSet
        )
    {
        ArgumentNullException.ThrowIfNull(dataContext, nameof(dataContext));
        ArgumentNullException.ThrowIfNull(tableType, nameof(tableType));

        var genericMethod = dropTableBaseMethod.MakeGenericMethod(tableType)!;        

        genericMethod.Invoke(dataContext, [dataContext, tableName, databaseName, schemaName, throwExceptionIfNotExists, serverName, tableOptions]);
    }

    public static int InsertOrReplace(
        this IDataContext dataContext
        , Type tableType
        , object obj
        , string? tableName = null
        , string? databaseName = null
        , string? schemaName = null
        , string? serverName = null
        , TableOptions tableOptions = TableOptions.NotSet
        )
    {
        ArgumentNullException.ThrowIfNull(dataContext, nameof(dataContext));
        ArgumentNullException.ThrowIfNull(tableType, nameof(tableType));

        if (tableType != obj.GetType())
            throw new ArgumentException(nameof(obj));

        var genericMethod = insertOrReplaceBaseMethod.MakeGenericMethod(tableType)!;

        return (int)genericMethod.Invoke(dataContext, [dataContext, obj, tableName, databaseName, schemaName, serverName, tableOptions] )!;
    }

    public static BulkCopyRowsCopied BulkCopy(
        this DataConnection dataConnection
        , Type tableType
        , BulkCopyOptions options
        , IEnumerable source
        )
    {
        ArgumentNullException.ThrowIfNull(dataConnection, nameof(dataConnection));
        ArgumentNullException.ThrowIfNull(tableType, nameof(tableType));

        var genericMethod = bulkCopyBaseMethod.MakeGenericMethod(tableType)!;

        return (BulkCopyRowsCopied)genericMethod.Invoke(dataConnection, [dataConnection, options, source])!;
    }

    public static ITable<dynamic> GetTable(
        this IDataContext dataContext
        , Type tableType
        ) 
    {
        ArgumentNullException.ThrowIfNull(dataContext, nameof(dataContext));
        ArgumentNullException.ThrowIfNull(tableType, nameof(tableType));

        var genericMethod = getTableBaseMethod.MakeGenericMethod(tableType)!;

        return (ITable<dynamic>)genericMethod.Invoke(dataContext, [dataContext])!;
    }    
}